Data Preparation


trend_vacc_hb <- daily_vacc_hb %>% 
  filter(hb_name == "Scotland") %>% 
  filter(sex =="Total") %>% 
  filter(age_group == "All vaccinations") %>% 
  filter(cumulative_number_vaccinated!=0) 

Analyse the trend on Vaccinations:

Plot1(a): Trend on Vaccination

#Plot to visualize trend on vaccination.
plot_vaccine <- trend_vacc_hb %>% 
  ggplot()+
  aes(x = date, y = number_vaccinated)+
  geom_line(aes(color = dose))+
  scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("People Vaccinated") +
  xlab("Year") +
  ylab("No of Positive Cases") +
  color_theme()+
  scale_colour_manual(values = c("#f1a340", "#5ab4ac"))

ggplotly(plot_vaccine)

Plot1(b): Cumulative Total on Vaccination

# Identify the population
dose_population <- daily_vacc_hb %>% 
  filter(sex == "Total") %>% 
  filter(date == max(date)) %>% 
  filter(hb_name =="Scotland") %>% 
  filter(age_group =="16 years and over") %>% 
  select(population) %>% 
  distinct()

#Plot to visualise cumulative vaccination trend.  
plot_vaccine_cumm <- trend_vacc_hb %>% 
  ggplot()+
  aes(x = date, y = cumulative_number_vaccinated)+
  geom_line(aes(color = dose))+
  scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Cummulative Trend on Vaccination") +
  xlab("Year") +
  ylab("No of People Vaccinated") +
  color_theme()+
  scale_colour_manual(values = c("#f1a340", "#5ab4ac"))+
  scale_y_continuous(labels = scales::unit_format(unit = "M", scale = 1e-6), 
                     sec.axis = sec_axis(trans = ~./dose_population$population,
                     name = "Percentage",
                     labels = scales::label_percent(accuracy = 0.01)
                    ))

plotly::ggplotly(plot_vaccine_cumm)
NA

Plot1(c): Cumulative Total on Vaccination based on Age group


cumm_vac_age<- daily_vacc_hb %>% 
  filter(sex == "Total") %>% 
  filter(is.na(age_group_qf)) %>% 
  filter(date == max(date)) %>% 
  filter(hb_name =="Scotland") %>% 
  filter(age_group !="All vaccinations") %>% 
  mutate (cumulative_percent_coverage = ifelse(cumulative_percent_coverage >100, 100,
                                               round(cumulative_percent_coverage,2))) %>% 
  select(dose,age_group, cumulative_percent_coverage, population)
  
cumm_vac_age_plot <- cumm_vac_age %>% 
  ggplot()+
  aes(x = age_group, y = cumulative_percent_coverage)+
  geom_col(aes(fill = dose), position = "dodge")+
   # geom_text(aes(label=cumulative_percent_coverage, hjust = 0),
   #           position=position_dodge(width=0.9),angle = 90)+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Percentage Coverage of Vaccination by age group") +
  xlab("Age group") +
  ylab("% of Coverage") +
  color_theme()+
  scale_fill_manual(values = c("#f1a340", "#5ab4ac"))

ggplotly(cumm_vac_age_plot)

#position=position_dodge(width=0.9), vjust=-0.25

Forecast on Vaccination: ARIMA Model

Data Preparation

trend_vacc_hb <- trend_vacc_hb %>% 
  filter (dose == "Dose 2") %>% 
  select(date,cumulative_number_vaccinated)

# Convert it to zoo type
daily_vacc_hb_zoo <- zoo(trend_vacc_hb$cumulative_number_vaccinated, 
           order.by=as.Date(trend_vacc_hb$date, format='%m/%d/%Y'))

# Convert it into a time series
daily_vacc_hb_timeseries <-timeSeries::as.timeSeries(daily_vacc_hb_zoo)

Step 1 : Visualize the time series

original_series<-
  autoplot(daily_vacc_hb_timeseries, colour = '#5ab4ac')+
  xlab("Month") + 
  ylab("VACCINATED")+
  #scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Original Series") +
  scale_y_continuous(labels = scales::unit_format(unit = "M", scale = 1e-6))+
  color_theme()

ggplotly(original_series)

Step 2 : Identification of model : (Finding d:)

Identify whether the time series is stationary / non stationary we can use ADF Augmented Dickey-Fuller test

adf_test <- adf.test(daily_vacc_hb_timeseries)

The time series is not stationary since we have a high p-value. So we apply difference

first_diff_ts<- diff(daily_vacc_hb_timeseries)
adf_test1 <- adf.test(na.omit(first_diff_ts))
second_diff_ts<- diff(first_diff_ts)
adf_test2 <- adf.test(na.omit(second_diff_ts))

adf_test1
adf_test2

Create a dataframe to compare

adf_data <- data.frame(Data = c("Original", "First-Ordered", "Second Ordered"),
                       Dickey_Fuller = c(adf_test$statistic, adf_test1$statistic, adf_test2$statistic),
                       p_value = c(adf_test$p.value,adf_test1$p.value,adf_test2$p.value))
adf_data

Initially the p-value is high which indicates that the Time Series is not stationary. So we apply difference 2 times. After the second difference, the p-value < significance level (0.05) So we can conclude that the difference data are stationary. So difference (d = 2)

Other method to confirm

ndiffs(daily_vacc_hb_timeseries)

Let’s plot the First Order and Second Order Difference Series

Order of first difference


first_order<- autoplot(first_diff_ts, ts.colour = '#5ab4ac') +
  xlab("Month") + 
  ylab("VACCINATED")+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("First-Order Difference") +
  color_theme()

ggplotly(first_order)

Order of Second difference


second_order<- autoplot(second_diff_ts, ts.colour = '#5ab4ac') +
  xlab("Month") + 
  ylab("VACCINATED")+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Second-Order Difference") +
  color_theme()

ggplotly(second_order)

Step 3 Estimate the parameters (Finding p and q)

For our model ARIMA (p,d,q), we found d = 2, the next step is to get the values of p and q, the order of AR and MA part. Plot ACF and PACF charts to identify q and p respectively.

The ACF and PACF plots of the differenced data show the following patterns:

The ACF doesn’t follow a sinusoidal pattern but its slowly geometric decay. Also there is a significant spike at lag 3 in the PACF, but none beyond lag 3. So the data may follow an AR(3) model

The PACF is sinusoidal and decaying. Also there is a significant spike at lag 2 in the ACF, but none beyond lag 2 So the data may follow an MA(2) model

So we propose three ARMA models for the differenced data: ARMA(p,q) ARMA(3,2), ARMA(3,0) and ARMA(0,2).

That is, for the original time series, we propose three ARIMA models,ARIMA(p,d,q) ARIMA(3,1,2), ARIMA(3,1,0) and ARMA(3,1,2).

Step 4 Build the ARIMA model

Manual ARIMA:

arima_fit1 = Arima(daily_vacc_hb_timeseries, order = c(3,1,2))
arima_fit2 = Arima(daily_vacc_hb_timeseries, order = c(3,1,0))
arima_fit3 = Arima(daily_vacc_hb_timeseries, order = c(3,1,2))
arima_fit4 = Arima(daily_vacc_hb_timeseries, order = c(3,1,1))
summary(arima_fit1)
summary(arima_fit2)
summary(arima_fit3)
summary(arima_fit4)

Forecast the Manual ARIMA model

Automated ARIMA

auto_arima_fit_vacc <- auto.arima(daily_vacc_hb_timeseries,
                  seasonal=FALSE,
                  stepwise = FALSE,
                  approximation = FALSE,
                  trace = TRUE
                  )
summary(auto_arima_fit_vacc)

Model Selection Criteria :

ARIMA models with minimum AIC, RMSE and MAPE criteria were chosen as the best models. Automated ARIMA confirms that the ARIMA(3, 2, 2) seems good based on AIC

lmtest::coeftest(auto_arima_fit_vacc)

All the coefficients are statistically significant.

Step 5 Check for Diagnostics

Let’s plot the diagnostics with the results to make sure the normality and correlation assumptions for the model hold. If the residuals look like white noise, proceed with forecast and prediction, otherwise repeat the model building.

res <- checkresiduals(auto_arima_fit_vacc, theme = color_theme())
res

The ACF plot of the residuals from the ARIMA(3,2,2) model shows that almost auto correlationswith regular interval outlier. A portmanteau test returns a smaller p-value (almost close to Zero), also suggesting that the residuals are white noise.

Fitting the ARIMA model with the existing data

The residual errors seem fine with near zero mean and uniform variance. Let’s plot the actuals against the fitted values

#Convert the model to dataframe for plotting

daily_vacc_hb_timeseries_data <- fortify(daily_vacc_hb_timeseries) %>% 
  clean_names() %>% 
  remove_rownames %>% 
  rename (date = index,
          vacc = data)%>% 
  mutate(index = seq(1:nrow(daily_vacc_hb_timeseries)))
  
arima_fit_resid <- ts(daily_vacc_hb_timeseries) - resid(auto_arima_fit_vacc)

arima_fit_data <- fortify(arima_fit_resid) %>% 
  clean_names() %>% 
  mutate(data = round(data,2))

fit_existing_data <- daily_vacc_hb_timeseries_data %>% 
  inner_join(arima_fit_data, by = c("index"))
#plotting the series along with the fitted values
fit_existing_data %>% 
  ggplot()+
  aes(x=date, y = vacc)+
  geom_line(color ="#5ab4ac")+
  geom_line(aes(x= date, y = data), colour = "red" )+
  xlab("Month") + 
  ylab("Number of People vaccinated")+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  ggtitle("Fitting the ARIMA model with existing data") +
  scale_y_continuous(labels = scales::unit_format(unit = "M", scale = 1e-6))+
  color_theme()

Step 6 Forecast using the model

Data Preparation :

#Convert the model to dataframe for plotting
forecast_model <- forecast(auto_arima_fit_vacc,level = c(80, 95), h = 60) 

forecast_model_data <- fortify(forecast_model) %>% 
  clean_names() %>% 
  mutate(data = round(data,2),
         fitted= round(fitted,2)) 

forecast_start_date <- as.Date(max(daily_vacc_hb_timeseries_data$date)+1)
forecast_end_date <- as.Date(forecast_start_date+59)

forecast_data <- forecast_model_data %>% 
  filter(!(is.na(point_forecast))) %>% 
  mutate(date = seq(forecast_start_date,forecast_end_date, by =1)) %>% 
select(-data,-fitted, -index)  

fitted_data <- forecast_model_data %>% 
  filter(!(is.na(data))) %>% 
  inner_join(daily_vacc_hb_timeseries_data, by = c("index")) %>% 
  mutate(date = as.Date(date)) %>% 
select(date, data, fitted) 

Plotting the Vaccination series plus the forecast and 95% prediction intervals

annotation <- data.frame(
   x = c(as.Date("03-04-2021","%d-%m-%Y"),as.Date("31-10-2021","%d-%m-%Y")),
   y = c(1000000,3000000),
   label = c("PAST", "FUTURE")
)

#Time series plots for the next 60 days according to best ARIMA models with 80%–95% CI.
fitted_data %>% 
  ggplot()+
  geom_line(aes(x= date, y = data))+
  geom_line(aes(x= date, y = fitted), colour = "red" )+
  geom_line(aes(x= date, y =point_forecast), data = forecast_data )+
  geom_ribbon(aes(x = date, y = point_forecast, ymin = lo_80, ymax = hi_80), 
              data = forecast_data, alpha = 0.3, fill = "green")+
  geom_ribbon(aes(x = date, y = point_forecast, ymin = lo_95, ymax = hi_95), 
              data = forecast_data, alpha = 0.1)+
  ggtitle("Forecast") +
  xlab("Month") + 
  ylab("Number of People vaccinated")+
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))+
  color_theme()+
  scale_y_continuous(labels = scales::unit_format(unit = "M", scale = 1e-6))+
  scale_x_date(breaks = "1 month", date_labels = "%b - %y" )+
   geom_text(data=annotation, 
             aes( x=x, y=y, label=label),                  
            color="red", 
            size=4 )+
  geom_vline(xintercept =as.Date("08-10-2021","%d-%m-%Y"), linetype = 2)
LS0tDQp0aXRsZTogIlBIU19DT1ZJRCBWYWNjaW5hdGlvbiBQcmVkaWN0aW9uIE1vZGVsIFVzaW5nIEFSSU1BIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQplZGl0b3Jfb3B0aW9uczogDQogIG1hcmtkb3duOiANCiAgICB3cmFwOiBzZW50ZW5jZQ0KLS0tDQoNCiMjIyBEYXRhIFByZXBhcmF0aW9uDQoNCmBgYHtyfQ0KDQp0cmVuZF92YWNjX2hiIDwtIGRhaWx5X3ZhY2NfaGIgJT4lIA0KICBmaWx0ZXIoaGJfbmFtZSA9PSAiU2NvdGxhbmQiKSAlPiUgDQogIGZpbHRlcihzZXggPT0iVG90YWwiKSAlPiUgDQogIGZpbHRlcihhZ2VfZ3JvdXAgPT0gIkFsbCB2YWNjaW5hdGlvbnMiKSAlPiUgDQogIGZpbHRlcihjdW11bGF0aXZlX251bWJlcl92YWNjaW5hdGVkIT0wKSANCg0KYGBgDQoNCiMjICoqQW5hbHlzZSB0aGUgdHJlbmQgb24gVmFjY2luYXRpb25zOioqDQoNCiMjIyAqKipQbG90MShhKTogVHJlbmQgb24gVmFjY2luYXRpb24qKioNCg0KYGBge3J9DQojUGxvdCB0byB2aXN1YWxpemUgdHJlbmQgb24gdmFjY2luYXRpb24uDQpwbG90X3ZhY2NpbmUgPC0gdHJlbmRfdmFjY19oYiAlPiUgDQogIGdncGxvdCgpKw0KICBhZXMoeCA9IGRhdGUsIHkgPSBudW1iZXJfdmFjY2luYXRlZCkrDQogIGdlb21fbGluZShhZXMoY29sb3IgPSBkb3NlKSkrDQogIHNjYWxlX3hfZGF0ZShicmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIC0gJXkiICkrDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpKw0KICBnZ3RpdGxlKCJQZW9wbGUgVmFjY2luYXRlZCIpICsNCiAgeGxhYigiWWVhciIpICsNCiAgeWxhYigiTm8gb2YgUG9zaXRpdmUgQ2FzZXMiKSArDQogIGNvbG9yX3RoZW1lKCkrDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiI2YxYTM0MCIsICIjNWFiNGFjIikpDQoNCmdncGxvdGx5KHBsb3RfdmFjY2luZSkNCmBgYA0KDQojIyMgKioqUGxvdDEoYik6IEN1bXVsYXRpdmUgVG90YWwgb24gVmFjY2luYXRpb24qKioNCg0KYGBge3J9DQojIElkZW50aWZ5IHRoZSBwb3B1bGF0aW9uDQpkb3NlX3BvcHVsYXRpb24gPC0gZGFpbHlfdmFjY19oYiAlPiUgDQogIGZpbHRlcihzZXggPT0gIlRvdGFsIikgJT4lIA0KICBmaWx0ZXIoZGF0ZSA9PSBtYXgoZGF0ZSkpICU+JSANCiAgZmlsdGVyKGhiX25hbWUgPT0iU2NvdGxhbmQiKSAlPiUgDQogIGZpbHRlcihhZ2VfZ3JvdXAgPT0iMTYgeWVhcnMgYW5kIG92ZXIiKSAlPiUgDQogIHNlbGVjdChwb3B1bGF0aW9uKSAlPiUgDQogIGRpc3RpbmN0KCkNCg0KI1Bsb3QgdG8gdmlzdWFsaXNlIGN1bXVsYXRpdmUgdmFjY2luYXRpb24gdHJlbmQuICANCnBsb3RfdmFjY2luZV9jdW1tIDwtIHRyZW5kX3ZhY2NfaGIgJT4lIA0KICBnZ3Bsb3QoKSsNCiAgYWVzKHggPSBkYXRlLCB5ID0gY3VtdWxhdGl2ZV9udW1iZXJfdmFjY2luYXRlZCkrDQogIGdlb21fbGluZShhZXMoY29sb3IgPSBkb3NlKSkrDQogIHNjYWxlX3hfZGF0ZShicmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIC0gJXkiICkrDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpKw0KICBnZ3RpdGxlKCJDdW1tdWxhdGl2ZSBUcmVuZCBvbiBWYWNjaW5hdGlvbiIpICsNCiAgeGxhYigiWWVhciIpICsNCiAgeWxhYigiTm8gb2YgUGVvcGxlIFZhY2NpbmF0ZWQiKSArDQogIGNvbG9yX3RoZW1lKCkrDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiI2YxYTM0MCIsICIjNWFiNGFjIikpKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjp1bml0X2Zvcm1hdCh1bml0ID0gIk0iLCBzY2FsZSA9IDFlLTYpLCANCiAgICAgICAgICAgICAgICAgICAgIHNlYy5heGlzID0gc2VjX2F4aXModHJhbnMgPSB+Li9kb3NlX3BvcHVsYXRpb24kcG9wdWxhdGlvbiwNCiAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiUGVyY2VudGFnZSIsDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX3BlcmNlbnQoYWNjdXJhY3kgPSAwLjAxKQ0KICAgICAgICAgICAgICAgICAgICApKQ0KDQpwbG90bHk6OmdncGxvdGx5KHBsb3RfdmFjY2luZV9jdW1tKQ0KDQpgYGANCg0KIyMjICoqKlBsb3QxKGMpOiBDdW11bGF0aXZlIFRvdGFsIG9uIFZhY2NpbmF0aW9uIGJhc2VkIG9uIEFnZSBncm91cCoqKg0KDQpgYGB7cn0NCmN1bW1fdmFjX2FnZTwtIGRhaWx5X3ZhY2NfaGIgJT4lIA0KICBmaWx0ZXIoc2V4ID09ICJUb3RhbCIpICU+JSANCiAgZmlsdGVyKGlzLm5hKGFnZV9ncm91cF9xZikpICU+JSANCiAgZmlsdGVyKGRhdGUgPT0gbWF4KGRhdGUpKSAlPiUgDQogIGZpbHRlcihoYl9uYW1lID09IlNjb3RsYW5kIikgJT4lIA0KICBmaWx0ZXIoYWdlX2dyb3VwICE9IkFsbCB2YWNjaW5hdGlvbnMiKSAlPiUgDQogIG11dGF0ZSAoY3VtdWxhdGl2ZV9wZXJjZW50X2NvdmVyYWdlID0gaWZlbHNlKGN1bXVsYXRpdmVfcGVyY2VudF9jb3ZlcmFnZSA+MTAwLCAxMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGN1bXVsYXRpdmVfcGVyY2VudF9jb3ZlcmFnZSwyKSkpICU+JSANCiAgc2VsZWN0KGRvc2UsYWdlX2dyb3VwLCBjdW11bGF0aXZlX3BlcmNlbnRfY292ZXJhZ2UsIHBvcHVsYXRpb24pDQogIA0KY3VtbV92YWNfYWdlX3Bsb3QgPC0gY3VtbV92YWNfYWdlICU+JSANCiAgZ2dwbG90KCkrDQogIGFlcyh4ID0gYWdlX2dyb3VwLCB5ID0gY3VtdWxhdGl2ZV9wZXJjZW50X2NvdmVyYWdlKSsNCiAgZ2VvbV9jb2woYWVzKGZpbGwgPSBkb3NlKSwgcG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgICMgZ2VvbV90ZXh0KGFlcyhsYWJlbD1jdW11bGF0aXZlX3BlcmNlbnRfY292ZXJhZ2UsIGhqdXN0ID0gMCksDQogICAjICAgICAgICAgICBwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSh3aWR0aD0wLjkpLGFuZ2xlID0gOTApKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKSsNCiAgZ2d0aXRsZSgiUGVyY2VudGFnZSBDb3ZlcmFnZSBvZiBWYWNjaW5hdGlvbiBieSBhZ2UgZ3JvdXAiKSArDQogIHhsYWIoIkFnZSBncm91cCIpICsNCiAgeWxhYigiJSBvZiBDb3ZlcmFnZSIpICsNCiAgY29sb3JfdGhlbWUoKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI2YxYTM0MCIsICIjNWFiNGFjIikpDQoNCmdncGxvdGx5KGN1bW1fdmFjX2FnZV9wbG90KQ0KDQojcG9zaXRpb249cG9zaXRpb25fZG9kZ2Uod2lkdGg9MC45KSwgdmp1c3Q9LTAuMjUNCg0KYGBgDQoNCiMgRm9yZWNhc3Qgb24gVmFjY2luYXRpb246IEFSSU1BIE1vZGVsDQoNCioqRGF0YSBQcmVwYXJhdGlvbioqDQoNCmBgYHtyfQ0KdHJlbmRfdmFjY19oYiA8LSB0cmVuZF92YWNjX2hiICU+JSANCiAgZmlsdGVyIChkb3NlID09ICJEb3NlIDIiKSAlPiUgDQogIHNlbGVjdChkYXRlLGN1bXVsYXRpdmVfbnVtYmVyX3ZhY2NpbmF0ZWQpDQoNCiMgQ29udmVydCBpdCB0byB6b28gdHlwZQ0KZGFpbHlfdmFjY19oYl96b28gPC0gem9vKHRyZW5kX3ZhY2NfaGIkY3VtdWxhdGl2ZV9udW1iZXJfdmFjY2luYXRlZCwgDQogICAgICAgICAgIG9yZGVyLmJ5PWFzLkRhdGUodHJlbmRfdmFjY19oYiRkYXRlLCBmb3JtYXQ9JyVtLyVkLyVZJykpDQoNCiMgQ29udmVydCBpdCBpbnRvIGEgdGltZSBzZXJpZXMNCmRhaWx5X3ZhY2NfaGJfdGltZXNlcmllcyA8LXRpbWVTZXJpZXM6OmFzLnRpbWVTZXJpZXMoZGFpbHlfdmFjY19oYl96b28pDQoNCmBgYA0KDQojIyBTdGVwIDEgOiBWaXN1YWxpemUgdGhlIHRpbWUgc2VyaWVzDQoNCmBgYHtyfQ0Kb3JpZ2luYWxfc2VyaWVzPC0NCiAgYXV0b3Bsb3QoZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzLCBjb2xvdXIgPSAnIzVhYjRhYycpKw0KICB4bGFiKCJNb250aCIpICsgDQogIHlsYWIoIlZBQ0NJTkFURUQiKSsNCiAgI3NjYWxlX3hfZGF0ZShicmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIC0gJXkiICkrDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpKw0KICBnZ3RpdGxlKCJPcmlnaW5hbCBTZXJpZXMiKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnVuaXRfZm9ybWF0KHVuaXQgPSAiTSIsIHNjYWxlID0gMWUtNikpKw0KICBjb2xvcl90aGVtZSgpDQoNCmdncGxvdGx5KG9yaWdpbmFsX3NlcmllcykNCmBgYA0KDQojIyBTdGVwIDIgOiBJZGVudGlmaWNhdGlvbiBvZiBtb2RlbCA6IChGaW5kaW5nIGQ6KQ0KDQpJZGVudGlmeSB3aGV0aGVyIHRoZSB0aW1lIHNlcmllcyBpcyBzdGF0aW9uYXJ5IC8gbm9uIHN0YXRpb25hcnkgd2UgY2FuIHVzZSBBREYgQXVnbWVudGVkIERpY2tleS1GdWxsZXIgdGVzdA0KDQpgYGB7cn0NCmFkZl90ZXN0IDwtIGFkZi50ZXN0KGRhaWx5X3ZhY2NfaGJfdGltZXNlcmllcykNCmBgYA0KDQpUaGUgdGltZSBzZXJpZXMgaXMgbm90IHN0YXRpb25hcnkgc2luY2Ugd2UgaGF2ZSBhIGhpZ2ggcC12YWx1ZS4NClNvIHdlIGFwcGx5IGRpZmZlcmVuY2UNCg0KYGBge3J9DQpmaXJzdF9kaWZmX3RzPC0gZGlmZihkYWlseV92YWNjX2hiX3RpbWVzZXJpZXMpDQphZGZfdGVzdDEgPC0gYWRmLnRlc3QobmEub21pdChmaXJzdF9kaWZmX3RzKSkNCnNlY29uZF9kaWZmX3RzPC0gZGlmZihmaXJzdF9kaWZmX3RzKQ0KYWRmX3Rlc3QyIDwtIGFkZi50ZXN0KG5hLm9taXQoc2Vjb25kX2RpZmZfdHMpKQ0KDQphZGZfdGVzdDENCmFkZl90ZXN0Mg0KYGBgDQoNCkNyZWF0ZSBhIGRhdGFmcmFtZSB0byBjb21wYXJlDQoNCmBgYHtyfQ0KYWRmX2RhdGEgPC0gZGF0YS5mcmFtZShEYXRhID0gYygiT3JpZ2luYWwiLCAiRmlyc3QtT3JkZXJlZCIsICJTZWNvbmQgT3JkZXJlZCIpLA0KICAgICAgICAgICAgICAgICAgICAgICBEaWNrZXlfRnVsbGVyID0gYyhhZGZfdGVzdCRzdGF0aXN0aWMsIGFkZl90ZXN0MSRzdGF0aXN0aWMsIGFkZl90ZXN0MiRzdGF0aXN0aWMpLA0KICAgICAgICAgICAgICAgICAgICAgICBwX3ZhbHVlID0gYyhhZGZfdGVzdCRwLnZhbHVlLGFkZl90ZXN0MSRwLnZhbHVlLGFkZl90ZXN0MiRwLnZhbHVlKSkNCmFkZl9kYXRhDQpgYGANCg0KSW5pdGlhbGx5IHRoZSBwLXZhbHVlIGlzIGhpZ2ggd2hpY2ggaW5kaWNhdGVzIHRoYXQgdGhlIFRpbWUgU2VyaWVzIGlzIG5vdCBzdGF0aW9uYXJ5Lg0KU28gd2UgYXBwbHkgZGlmZmVyZW5jZSAyIHRpbWVzLg0KQWZ0ZXIgdGhlIHNlY29uZCBkaWZmZXJlbmNlLCB0aGUgcC12YWx1ZSBcPCBzaWduaWZpY2FuY2UgbGV2ZWwgKDAuMDUpIFNvIHdlIGNhbiBjb25jbHVkZSB0aGF0IHRoZSBkaWZmZXJlbmNlIGRhdGEgYXJlIHN0YXRpb25hcnkuDQpTbyBkaWZmZXJlbmNlIChkID0gMikNCg0KT3RoZXIgbWV0aG9kIHRvIGNvbmZpcm0NCg0KYGBge3J9DQpuZGlmZnMoZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzKQ0KYGBgDQoNCkxldCdzIHBsb3QgdGhlIEZpcnN0IE9yZGVyIGFuZCBTZWNvbmQgT3JkZXIgRGlmZmVyZW5jZSBTZXJpZXMNCg0KT3JkZXIgb2YgZmlyc3QgZGlmZmVyZW5jZQ0KDQpgYGB7cn0NCg0KZmlyc3Rfb3JkZXI8LSBhdXRvcGxvdChmaXJzdF9kaWZmX3RzLCB0cy5jb2xvdXIgPSAnIzVhYjRhYycpICsNCiAgeGxhYigiTW9udGgiKSArIA0KICB5bGFiKCJWQUNDSU5BVEVEIikrDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpKw0KICBnZ3RpdGxlKCJGaXJzdC1PcmRlciBEaWZmZXJlbmNlIikgKw0KICBjb2xvcl90aGVtZSgpDQoNCmdncGxvdGx5KGZpcnN0X29yZGVyKQ0KYGBgDQoNCk9yZGVyIG9mIFNlY29uZCBkaWZmZXJlbmNlDQoNCmBgYHtyfQ0KDQpzZWNvbmRfb3JkZXI8LSBhdXRvcGxvdChzZWNvbmRfZGlmZl90cywgdHMuY29sb3VyID0gJyM1YWI0YWMnKSArDQogIHhsYWIoIk1vbnRoIikgKyANCiAgeWxhYigiVkFDQ0lOQVRFRCIpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKSsNCiAgZ2d0aXRsZSgiU2Vjb25kLU9yZGVyIERpZmZlcmVuY2UiKSArDQogIGNvbG9yX3RoZW1lKCkNCg0KZ2dwbG90bHkoc2Vjb25kX29yZGVyKQ0KYGBgDQoNCiMjIFN0ZXAgMyBFc3RpbWF0ZSB0aGUgcGFyYW1ldGVycyAoRmluZGluZyBwIGFuZCBxKQ0KDQpGb3Igb3VyIG1vZGVsIEFSSU1BIChwLGQscSksIHdlIGZvdW5kIGQgPSAyLCB0aGUgbmV4dCBzdGVwIGlzIHRvIGdldCB0aGUgdmFsdWVzIG9mIHAgYW5kIHEsIHRoZSBvcmRlciBvZiBBUiBhbmQgTUEgcGFydC4NClBsb3QgQUNGIGFuZCBQQUNGIGNoYXJ0cyB0byBpZGVudGlmeSBxIGFuZCBwIHJlc3BlY3RpdmVseS4NCg0KYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnBhcihtZnJvdz1jKDIsMikpDQogIGFjZjEoZmlyc3RfZGlmZl90cywgY29sPTI6NywgbHdkPTQpDQogIGFjZjEoc2Vjb25kX2RpZmZfdHMsIGNvbD0yOjcsIGx3ZD00KQ0KICBhY2YxKGZpcnN0X2RpZmZfdHMsIHBhY2YgPSBUUlVFLCBjb2w9Mjo3LCBsd2Q9NCkNCiAgYWNmMShzZWNvbmRfZGlmZl90cywgcGFjZiA9IFRSVUUsIGNvbD0yOjcsIGx3ZD00KQ0KYGBgDQoNClRoZSBBQ0YgYW5kIFBBQ0YgcGxvdHMgb2YgdGhlIGRpZmZlcmVuY2VkIGRhdGEgc2hvdyB0aGUgZm9sbG93aW5nIHBhdHRlcm5zOg0KDQpUaGUgQUNGIGRvZXNuJ3QgZm9sbG93IGEgc2ludXNvaWRhbCBwYXR0ZXJuIGJ1dCBpdHMgc2xvd2x5IGdlb21ldHJpYyBkZWNheS4NCkFsc28gdGhlcmUgaXMgYSBzaWduaWZpY2FudCBzcGlrZSBhdCBsYWcgMyBpbiB0aGUgUEFDRiwgYnV0IG5vbmUgYmV5b25kIGxhZyAzLg0KU28gdGhlIGRhdGEgbWF5IGZvbGxvdyBhbiBBUigzKSBtb2RlbA0KDQpUaGUgUEFDRiBpcyBzaW51c29pZGFsIGFuZCBkZWNheWluZy4NCkFsc28gdGhlcmUgaXMgYSBzaWduaWZpY2FudCBzcGlrZSBhdCBsYWcgMiBpbiB0aGUgQUNGLCBidXQgbm9uZSBiZXlvbmQgbGFnIDIgU28gdGhlIGRhdGEgbWF5IGZvbGxvdyBhbiBNQSgyKSBtb2RlbA0KDQpTbyB3ZSBwcm9wb3NlIHRocmVlIEFSTUEgbW9kZWxzIGZvciB0aGUgZGlmZmVyZW5jZWQgZGF0YTogQVJNQShwLHEpIEFSTUEoMywyKSwgQVJNQSgzLDApIGFuZCBBUk1BKDAsMikuDQoNClRoYXQgaXMsIGZvciB0aGUgb3JpZ2luYWwgdGltZSBzZXJpZXMsIHdlIHByb3Bvc2UgdGhyZWUgQVJJTUEgbW9kZWxzLEFSSU1BKHAsZCxxKSBBUklNQSgzLDEsMiksIEFSSU1BKDMsMSwwKSBhbmQgQVJNQSgzLDEsMikuDQoNCiMjIFN0ZXAgNCBCdWlsZCB0aGUgQVJJTUEgbW9kZWwNCg0KIyMjIE1hbnVhbCBBUklNQToNCg0KYGBge3J9DQphcmltYV9maXQxID0gQXJpbWEoZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzLCBvcmRlciA9IGMoMywxLDIpKQ0KYXJpbWFfZml0MiA9IEFyaW1hKGRhaWx5X3ZhY2NfaGJfdGltZXNlcmllcywgb3JkZXIgPSBjKDMsMSwwKSkNCmFyaW1hX2ZpdDMgPSBBcmltYShkYWlseV92YWNjX2hiX3RpbWVzZXJpZXMsIG9yZGVyID0gYygzLDEsMikpDQphcmltYV9maXQ0ID0gQXJpbWEoZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzLCBvcmRlciA9IGMoMywxLDEpKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShhcmltYV9maXQxKQ0Kc3VtbWFyeShhcmltYV9maXQyKQ0Kc3VtbWFyeShhcmltYV9maXQzKQ0Kc3VtbWFyeShhcmltYV9maXQ0KQ0KYGBgDQoNCkZvcmVjYXN0IHRoZSBNYW51YWwgQVJJTUEgbW9kZWwNCg0KYGBge3J9DQojIEZvcmVjYXN0IHRoZSBtYW51YWwgbW9kZWxzDQoNCmZ1dHVyZSA9IGZvcmVjYXN0KGFyaW1hX2ZpdDEsIGggPSAzMCkNCmZ1dHVyZTIgPSBmb3JlY2FzdChhcmltYV9maXQyLCBoID0gMzApDQpmdXR1cmUzID0gZm9yZWNhc3QoYXJpbWFfZml0MywgaCA9IDMwKQ0KZnV0dXJlNCA9IGZvcmVjYXN0KGFyaW1hX2ZpdDQsIGggPSAzMCkNCg0KI1Bsb3QgdGhlIGZvcmVjYXN0ZWQgbWFudWFsIG1vZGVscw0KDQpwYXIobWZyb3cgPSBjKDIsMikpDQpwbG90KGZ1dHVyZSkNCnBsb3QoZnV0dXJlMikNCnBsb3QoZnV0dXJlMykNCnBsb3QoZnV0dXJlNCkNCmBgYA0KDQojIyMgKipBdXRvbWF0ZWQgQVJJTUEqKg0KDQpgYGB7cn0NCmF1dG9fYXJpbWFfZml0X3ZhY2MgPC0gYXV0by5hcmltYShkYWlseV92YWNjX2hiX3RpbWVzZXJpZXMsDQogICAgICAgICAgICAgICAgICBzZWFzb25hbD1GQUxTRSwNCiAgICAgICAgICAgICAgICAgIHN0ZXB3aXNlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICBhcHByb3hpbWF0aW9uID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICB0cmFjZSA9IFRSVUUNCiAgICAgICAgICAgICAgICAgICkNCnN1bW1hcnkoYXV0b19hcmltYV9maXRfdmFjYykNCmBgYA0KDQoqKk1vZGVsIFNlbGVjdGlvbiBDcml0ZXJpYSA6KioNCg0KQVJJTUEgbW9kZWxzIHdpdGggbWluaW11bSBBSUMsIFJNU0UgYW5kIE1BUEUgY3JpdGVyaWEgd2VyZSBjaG9zZW4gYXMgdGhlIGJlc3QgbW9kZWxzLg0KQXV0b21hdGVkIEFSSU1BIGNvbmZpcm1zIHRoYXQgdGhlIEFSSU1BKDMsIDIsIDIpIHNlZW1zIGdvb2QgYmFzZWQgb24gQUlDDQoNCmBgYHtyfQ0KbG10ZXN0Ojpjb2VmdGVzdChhdXRvX2FyaW1hX2ZpdF92YWNjKQ0KYGBgDQoNCkFsbCB0aGUgY29lZmZpY2llbnRzIGFyZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50Lg0KDQojIyBTdGVwIDUgQ2hlY2sgZm9yIERpYWdub3N0aWNzDQoNCkxldCdzIHBsb3QgdGhlIGRpYWdub3N0aWNzIHdpdGggdGhlIHJlc3VsdHMgdG8gbWFrZSBzdXJlIHRoZSBub3JtYWxpdHkgYW5kIGNvcnJlbGF0aW9uIGFzc3VtcHRpb25zIGZvciB0aGUgbW9kZWwgaG9sZC4NCklmIHRoZSByZXNpZHVhbHMgbG9vayBsaWtlIHdoaXRlIG5vaXNlLCBwcm9jZWVkIHdpdGggZm9yZWNhc3QgYW5kIHByZWRpY3Rpb24sIG90aGVyd2lzZSByZXBlYXQgdGhlIG1vZGVsIGJ1aWxkaW5nLg0KDQpgYGB7cn0NCnJlcyA8LSBjaGVja3Jlc2lkdWFscyhhdXRvX2FyaW1hX2ZpdF92YWNjLCB0aGVtZSA9IGNvbG9yX3RoZW1lKCkpDQpyZXMNCmBgYA0KDQpUaGUgQUNGIHBsb3Qgb2YgdGhlIHJlc2lkdWFscyBmcm9tIHRoZSBBUklNQSgzLDIsMikgbW9kZWwgc2hvd3MgdGhhdCBhbG1vc3QgYXV0byBjb3JyZWxhdGlvbnN3aXRoIHJlZ3VsYXIgaW50ZXJ2YWwgb3V0bGllci4NCkEgcG9ydG1hbnRlYXUgdGVzdCByZXR1cm5zIGEgc21hbGxlciBwLXZhbHVlIChhbG1vc3QgY2xvc2UgdG8gWmVybyksIGFsc28gc3VnZ2VzdGluZyB0aGF0IHRoZSByZXNpZHVhbHMgYXJlIHdoaXRlIG5vaXNlLg0KDQoqKkZpdHRpbmcgdGhlIEFSSU1BIG1vZGVsIHdpdGggdGhlIGV4aXN0aW5nIGRhdGEqKg0KDQpUaGUgcmVzaWR1YWwgZXJyb3JzIHNlZW0gZmluZSB3aXRoIG5lYXIgemVybyBtZWFuIGFuZCB1bmlmb3JtIHZhcmlhbmNlLg0KTGV0J3MgcGxvdCB0aGUgYWN0dWFscyBhZ2FpbnN0IHRoZSBmaXR0ZWQgdmFsdWVzDQoNCmBgYHtyfQ0KI0NvbnZlcnQgdGhlIG1vZGVsIHRvIGRhdGFmcmFtZSBmb3IgcGxvdHRpbmcNCg0KZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzX2RhdGEgPC0gZm9ydGlmeShkYWlseV92YWNjX2hiX3RpbWVzZXJpZXMpICU+JSANCiAgY2xlYW5fbmFtZXMoKSAlPiUgDQogIHJlbW92ZV9yb3duYW1lcyAlPiUgDQogIHJlbmFtZSAoZGF0ZSA9IGluZGV4LA0KICAgICAgICAgIHZhY2MgPSBkYXRhKSU+JSANCiAgbXV0YXRlKGluZGV4ID0gc2VxKDE6bnJvdyhkYWlseV92YWNjX2hiX3RpbWVzZXJpZXMpKSkNCiAgDQphcmltYV9maXRfcmVzaWQgPC0gdHMoZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzKSAtIHJlc2lkKGF1dG9fYXJpbWFfZml0X3ZhY2MpDQoNCmFyaW1hX2ZpdF9kYXRhIDwtIGZvcnRpZnkoYXJpbWFfZml0X3Jlc2lkKSAlPiUgDQogIGNsZWFuX25hbWVzKCkgJT4lIA0KICBtdXRhdGUoZGF0YSA9IHJvdW5kKGRhdGEsMikpDQoNCmZpdF9leGlzdGluZ19kYXRhIDwtIGRhaWx5X3ZhY2NfaGJfdGltZXNlcmllc19kYXRhICU+JSANCiAgaW5uZXJfam9pbihhcmltYV9maXRfZGF0YSwgYnkgPSBjKCJpbmRleCIpKQ0KYGBgDQoNCmBgYHtyfQ0KI3Bsb3R0aW5nIHRoZSBzZXJpZXMgYWxvbmcgd2l0aCB0aGUgZml0dGVkIHZhbHVlcw0KZml0X2V4aXN0aW5nX2RhdGEgJT4lIA0KICBnZ3Bsb3QoKSsNCiAgYWVzKHg9ZGF0ZSwgeSA9IHZhY2MpKw0KICBnZW9tX2xpbmUoY29sb3IgPSIjNWFiNGFjIikrDQogIGdlb21fbGluZShhZXMoeD0gZGF0ZSwgeSA9IGRhdGEpLCBjb2xvdXIgPSAicmVkIiApKw0KICB4bGFiKCJNb250aCIpICsgDQogIHlsYWIoIk51bWJlciBvZiBQZW9wbGUgdmFjY2luYXRlZCIpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKSsNCiAgZ2d0aXRsZSgiRml0dGluZyB0aGUgQVJJTUEgbW9kZWwgd2l0aCBleGlzdGluZyBkYXRhIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjp1bml0X2Zvcm1hdCh1bml0ID0gIk0iLCBzY2FsZSA9IDFlLTYpKSsNCiAgY29sb3JfdGhlbWUoKQ0KYGBgDQoNCiMjIFN0ZXAgNiBGb3JlY2FzdCB1c2luZyB0aGUgbW9kZWwNCg0KKipEYXRhIFByZXBhcmF0aW9uIDoqKg0KDQpgYGB7cn0NCiNDb252ZXJ0IHRoZSBtb2RlbCB0byBkYXRhZnJhbWUgZm9yIHBsb3R0aW5nDQpmb3JlY2FzdF9tb2RlbCA8LSBmb3JlY2FzdChhdXRvX2FyaW1hX2ZpdF92YWNjLGxldmVsID0gYyg4MCwgOTUpLCBoID0gNjApIA0KDQpmb3JlY2FzdF9tb2RlbF9kYXRhIDwtIGZvcnRpZnkoZm9yZWNhc3RfbW9kZWwpICU+JSANCiAgY2xlYW5fbmFtZXMoKSAlPiUgDQogIG11dGF0ZShkYXRhID0gcm91bmQoZGF0YSwyKSwNCiAgICAgICAgIGZpdHRlZD0gcm91bmQoZml0dGVkLDIpKSANCg0KZm9yZWNhc3Rfc3RhcnRfZGF0ZSA8LSBhcy5EYXRlKG1heChkYWlseV92YWNjX2hiX3RpbWVzZXJpZXNfZGF0YSRkYXRlKSsxKQ0KZm9yZWNhc3RfZW5kX2RhdGUgPC0gYXMuRGF0ZShmb3JlY2FzdF9zdGFydF9kYXRlKzU5KQ0KDQpmb3JlY2FzdF9kYXRhIDwtIGZvcmVjYXN0X21vZGVsX2RhdGEgJT4lIA0KICBmaWx0ZXIoIShpcy5uYShwb2ludF9mb3JlY2FzdCkpKSAlPiUgDQogIG11dGF0ZShkYXRlID0gc2VxKGZvcmVjYXN0X3N0YXJ0X2RhdGUsZm9yZWNhc3RfZW5kX2RhdGUsIGJ5ID0xKSkgJT4lIA0Kc2VsZWN0KC1kYXRhLC1maXR0ZWQsIC1pbmRleCkgIA0KDQpmaXR0ZWRfZGF0YSA8LSBmb3JlY2FzdF9tb2RlbF9kYXRhICU+JSANCiAgZmlsdGVyKCEoaXMubmEoZGF0YSkpKSAlPiUgDQogIGlubmVyX2pvaW4oZGFpbHlfdmFjY19oYl90aW1lc2VyaWVzX2RhdGEsIGJ5ID0gYygiaW5kZXgiKSkgJT4lIA0KICBtdXRhdGUoZGF0ZSA9IGFzLkRhdGUoZGF0ZSkpICU+JSANCnNlbGVjdChkYXRlLCBkYXRhLCBmaXR0ZWQpIA0KDQpgYGANCg0KKipQbG90dGluZyB0aGUgVmFjY2luYXRpb24gc2VyaWVzIHBsdXMgdGhlIGZvcmVjYXN0IGFuZCA5NSUgcHJlZGljdGlvbiBpbnRlcnZhbHMqKg0KDQpgYGB7cn0NCmFubm90YXRpb24gPC0gZGF0YS5mcmFtZSgNCiAgIHggPSBjKGFzLkRhdGUoIjAzLTA0LTIwMjEiLCIlZC0lbS0lWSIpLGFzLkRhdGUoIjMxLTEwLTIwMjEiLCIlZC0lbS0lWSIpKSwNCiAgIHkgPSBjKDEwMDAwMDAsMzAwMDAwMCksDQogICBsYWJlbCA9IGMoIlBBU1QiLCAiRlVUVVJFIikNCikNCg0KI1RpbWUgc2VyaWVzIHBsb3RzIGZvciB0aGUgbmV4dCA2MCBkYXlzIGFjY29yZGluZyB0byBiZXN0IEFSSU1BIG1vZGVscyB3aXRoIDgwJeKAkzk1JSBDSS4NCmZpdHRlZF9kYXRhICU+JSANCiAgZ2dwbG90KCkrDQogIGdlb21fbGluZShhZXMoeD0gZGF0ZSwgeSA9IGRhdGEpKSsNCiAgZ2VvbV9saW5lKGFlcyh4PSBkYXRlLCB5ID0gZml0dGVkKSwgY29sb3VyID0gInJlZCIgKSsNCiAgZ2VvbV9saW5lKGFlcyh4PSBkYXRlLCB5ID1wb2ludF9mb3JlY2FzdCksIGRhdGEgPSBmb3JlY2FzdF9kYXRhICkrDQogIGdlb21fcmliYm9uKGFlcyh4ID0gZGF0ZSwgeSA9IHBvaW50X2ZvcmVjYXN0LCB5bWluID0gbG9fODAsIHltYXggPSBoaV84MCksIA0KICAgICAgICAgICAgICBkYXRhID0gZm9yZWNhc3RfZGF0YSwgYWxwaGEgPSAwLjMsIGZpbGwgPSAiZ3JlZW4iKSsNCiAgZ2VvbV9yaWJib24oYWVzKHggPSBkYXRlLCB5ID0gcG9pbnRfZm9yZWNhc3QsIHltaW4gPSBsb185NSwgeW1heCA9IGhpXzk1KSwgDQogICAgICAgICAgICAgIGRhdGEgPSBmb3JlY2FzdF9kYXRhLCBhbHBoYSA9IDAuMSkrDQogIGdndGl0bGUoIkZvcmVjYXN0IikgKw0KICB4bGFiKCJNb250aCIpICsgDQogIHlsYWIoIk51bWJlciBvZiBQZW9wbGUgdmFjY2luYXRlZCIpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKSsNCiAgY29sb3JfdGhlbWUoKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6dW5pdF9mb3JtYXQodW5pdCA9ICJNIiwgc2NhbGUgPSAxZS02KSkrDQogIHNjYWxlX3hfZGF0ZShicmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViIC0gJXkiICkrDQogICBnZW9tX3RleHQoZGF0YT1hbm5vdGF0aW9uLCANCiAgICAgICAgICAgICBhZXMoIHg9eCwgeT15LCBsYWJlbD1sYWJlbCksICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICBjb2xvcj0icmVkIiwgDQogICAgICAgICAgICBzaXplPTQgKSsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID1hcy5EYXRlKCIwOC0xMC0yMDIxIiwiJWQtJW0tJVkiKSwgbGluZXR5cGUgPSAyKQ0KYGBgDQo=